package com.wolfking.spi;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;

public interface SpiBaseInterface<T, E> {

    /**
     * 返回spi的code
     * @return
     */
    E getSpiCode();

    Map<Object, Set<Object>> BEAN_MAP = Maps.newConcurrentMap();

    Map<Class<?>, Class<?>> CLASS_MAP = Maps.newConcurrentMap();

    Map<String, Object> BEAN_SECOND_MAP = Maps.newConcurrentMap();

    @SuppressWarnings("all")
    default T choice(E e) {

        String key = String.format("%s_#_%s", getClass().getName(), e.toString());
        if (BEAN_SECOND_MAP.containsKey(key)) {
            return (T) BEAN_SECOND_MAP.get(key);
        } else {
            Class<T> clazz = (Class<T>) CLASS_MAP.get(getClass());
            Set<Type> typeSet = Sets.newHashSet();
            Type[] genericInterfaces = getClass().getGenericInterfaces();
            List<Type> originList = Lists.newArrayList(genericInterfaces);
            List<Type> redoList = Lists.newArrayList();
            int i = 0;
            while (i < 10 && clazz == null) {
                if (originList.size() == 0) {
                    break;
                }
                for (Type type : originList) {
                    if (!typeSet.contains(type)) {
                        if (type instanceof Class) {
                            redoList.addAll(Lists.newArrayList(((Class<?>) type).getGenericInterfaces()));
                        }
                        if (type instanceof ParameterizedType) {
                            ParameterizedType parameterizedType = (ParameterizedType) type;
                            if (parameterizedType.getRawType().equals(SpiBaseInterface.class)) {
                                clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];
                                if (!CLASS_MAP.containsKey(getClass())) {
                                    synchronized (SpiBaseInterface.class) {
                                        if (!CLASS_MAP.containsKey(getClass())) {
                                            CLASS_MAP.put(getClass(), clazz);
                                        }
                                    }
                                }
                                break;
                            }
                        }
                        typeSet.add(type);
                    }
                }
                originList = redoList;
                redoList = Lists.newArrayList();
                i++;
            }
            if (clazz != null) {
                Set<Object> objects = BEAN_MAP.get(e);
                if (objects != null) {
                    for (Object obj : objects) {
                        if (clazz.isAssignableFrom(obj.getClass())) {
                            if (!BEAN_SECOND_MAP.containsKey(key)) {
                                synchronized (SpiBaseInterface.class) {
                                    if (!BEAN_SECOND_MAP.containsKey(key)) {
                                        BEAN_SECOND_MAP.put(key, obj);
                                    }
                                }
                            }
                            return (T) obj;
                        }
                    }
                }
            }
            return null;
        }
    }

    @SuppressWarnings("all")
    static void register(SpiBaseInterface bean) {
        synchronized (SpiBaseInterface.class) {
            Set<Object> objects = BEAN_MAP.get(bean.getSpiCode());
            if (objects == null) {
                objects = Sets.newHashSet();
            }
            objects.add(bean);
            BEAN_MAP.put(bean.getSpiCode(), objects);
        }
    }
}
